/**
 * Class: MidiAnalyst
 *
 * Purpose: 
 *
 * Created: Mar 7, 2010 @ 9:44:35 AM
 * @author Ken
 */
package org.openfantasia.musica.midi;

import jm.music.data.Note;
import jm.music.data.Part;
import jm.music.data.Phrase;
import jm.music.data.Score;
import jm.util.Read;
import org.openfantasia.time.Ticker;
import org.openfantasia.time.Timebase;
import org.openfantasia.util.listener.DoubleScalar;
import org.openfantasia.util.listener.Event;
import org.openfantasia.util.listener.ListenerManager;

import javax.sound.midi.InvalidMidiDataException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class MidiAnalyst {

	public static void main(String[] args) throws InvalidMidiDataException, IOException {
		String filename = "C:\\ART TOOLS\\MIDI\\enya-orinoco_flow.mid";
		MidiAnalyst analyst = new MidiAnalyst(filename);

		Timebase timebase = new Timebase();
		ListenerManager mgr = new ListenerManager();
		analyst.play(timebase, mgr, 0.0);
		timebase.setRunning(true);
	}


	protected String filename;

	protected Score score;

	protected double skew;


	public MidiAnalyst(String filename) {
		this.filename = filename;
		score = new Score();
		Read.midi(score, filename);
	}


	public int getPartCount() {
		return score.getPartArray().length;
	}


	public int getPhraseCount(int partNumber) {
		return score.getPartArray()[partNumber].getPhraseArray().length;
	}


	public void play(Timebase timebase, final ListenerManager manager, double skew) {
		this.skew = skew;
		timebase.reset(0.0, score.getEndTime());

		final double scoreVolume = score.getVolume();
		double scoreTempo = score.getTempo();
		final Part[] parts = score.getPartArray();
		for (int partNumber = 0; partNumber < parts.length; partNumber++) {
			final Part part = parts[partNumber];
			final double partVolume = part.getVolume();
			double partTempo = part.getTempo();
			if (partTempo == -1) {
				partTempo = scoreTempo;
			}
			int partInstrument = part.getInstrument();

			Phrase[] phrases = part.getPhraseArray();
			for (int phraseNumber = 0; phraseNumber < phrases.length; phraseNumber++) {
				Phrase phrase = phrases[phraseNumber];
				final double startTime = phrase.getStartTime();
				final double endTime = phrase.getEndTime();
				final double phraseVolume = phrase.getVolume();
				double phraseTempo = phrase.getTempo();
				if (phraseTempo == -1) {
					phraseTempo = partTempo;
				}
				int phraseInstrument = phrase.getInstrument();
				if (phraseInstrument == -1) {
					phraseInstrument = partInstrument;
				}

				double t = startTime + skew;
				Note[] noteArray = phrase.getNoteArray();
				for (int noteNumber = 0; noteNumber < noteArray.length; noteNumber++) {
					Note note = noteArray[noteNumber];
					note.setOffset(t);
					t += note.getDuration();
				}
				final LinkedList<Note> notes = new LinkedList<Note>(Arrays.asList(noteArray));

				final int partNum = partNumber;
				final int phraseNum = phraseNumber;
				final double volume = scoreVolume * partVolume * phraseVolume / 1e6;
				final int instr = phraseInstrument;
				final double beatsPerSecond = phraseTempo / 60.0;
				final double tempoPeriod = (1.0 / beatsPerSecond);
				final double tickerPeriod = tempoPeriod / 1000.0;
				Ticker ticker = new Ticker(tickerPeriod) {
					public void receive(Event event) {
						DoubleScalar timeEvent = (DoubleScalar) event;
						double time = timeEvent.getTime();
						List<Note> activeNotes = new ArrayList<Note>();
						double realtime = time / tempoPeriod;
						if ((startTime <= realtime) && (realtime < endTime)) {
							boolean done = false;
							while (!done && !notes.isEmpty()) {
								Note note = notes.getFirst();
								if (note.getOffset() <= time) {
									notes.remove(note);
									activeNotes.add(note);
								}
								else {
									done = true;
								}
							}
						}

						for (Note note : activeNotes) {
							String ch = createMidiName(partNum, phraseNum);
							NoteMessage message = new NoteMessage(partNum, phraseNum, volume, instr, note);
							message.setTime(time);
							message.setChannel(ch);
							manager.send(ch, message);
							System.out.println(message);
						}
					}
				};
				timebase.addTicker(ticker);
			}
		}
	}


	public static String createMidiName(int partNum, int phraseNum) {
		return "midi-" + partNum + ":" + phraseNum;
	}


	public static class NoteMessage extends Event {

		private int part;

		private int phrase;

		private double volume;

		private int instrumentId;

		private Note note;


		public NoteMessage(int part, int phrase, double volume, int instrumentId, Note note) {
			this.part = part;
			this.phrase = phrase;
			this.volume = volume;
			this.instrumentId = instrumentId;
			this.note = note;
		}


		public int getPart() {
			return part;
		}


		public int getPhrase() {
			return phrase;
		}


		public Note getNote() {
			return note;
		}


		public int getInstrumentId() {
			return instrumentId;
		}


		public double getTime() {
			return note.getOffset();
		}


		public double getPan() {
			return note.getPan();
		}


		public double getVolume() {
			return volume;
		}


		public double getDuration() {
			return note.getDuration();
		}


		public double getFrequency() {
			return note.getFrequency();
		}


		public double getRhythmValue() {
			return note.getRhythmValue();
		}


		public int getDynamic() {
			return note.getDynamic();
		}


		public int getPitch() {
			return note.getPitch();
		}


		public int getOctave() {
			return note.getPitch() / 12;
		}


		public boolean getPitchType() {
			return note.getPitchType();
		}


		public double getSampleStartTime() {
			return note.getSampleStartTime();
		}


		public String getNoteName() {
			return note.getNote();
		}


		public String getFullName() {
			return note.getNote() + getOctave();
		}


		public String toString() {
			StringBuilder sb = new StringBuilder();

			sb.append("NoteEvent{");
			sb.append(" time: " + getTime());
			sb.append("; part " + getPart());
			sb.append("; phrase " + getPhrase());
			sb.append("; volume: " + getVolume());
			sb.append("; pan: " + getPan());
			sb.append("; pitch: " + getPitch() + " (" + getFullName() + " - " + getFrequency() + ")");
			sb.append("; duration: " + getDuration());
			sb.append("; rhythmValue: " + getRhythmValue());
			sb.append("; dynamic: " + getDynamic());
			sb.append(" }");

			return sb.toString();
		}
	}
}
